﻿using System.Threading;
using System.Threading.Tasks;

namespace Sidvall.Security
{
    public class AuthenticationStrategy : IAuthenticationStrategy, System.IDisposable
    {
        #region Public Members

        public Sidvall.Net.AuthenticationHeader CurrentAuthenticationHeader { get; set; }
        public TokenItem TokenItem { get; protected set; }
        public bool UseTokenFile { get; set; }
        public string AuthorityTokenEndpoint { get; set; }
        public string ClientId { get; set; }
        public string ClientSecret { get; set; }
        protected bool IsSaving { get; set; }
        #region TokenFilePath

        protected virtual string TokenFilePath
        {
            get
            {
                return "token.json";
            }
        }

        #endregion
        #region TokenItemIsLoaded

        private bool _TokenItemIsLoaded;
        public virtual bool TokenItemIsLoaded
        {
            get
            {
                return _TokenItemIsLoaded;
            }
            protected set
            {
                _TokenItemIsLoaded = value;
            }
        }

        #endregion

        #region Dispose

        public void Dispose()
        {
            Dispose(true);
            System.GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                SecurityManager.AuthenticationHeaderChanged -= OnAuthenticationHeaderChanged;
            }
        }

        #endregion
        #region GetTokenFilePathAsync

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        public virtual async Task<string> GetTokenFilePathAsync()
        {
            if ((this.UseTokenFile) && (Sidvall.SystemContext.Current.UserStorageManager != null))
            {
                try
                {
                    if (await Sidvall.SystemContext.Current.UserStorageManager.FileExistsAsync(this.TokenFilePath).ConfigureAwait(false))
                        return Sidvall.SystemContext.Current.UserStorageManager.GetPath(this.TokenFilePath);
                }
                catch (System.Exception)
                {
                    return null;
                }
            }
            return null;
        }

        #endregion
        #region LoadAuthenticationHeader

        protected virtual void LoadAuthenticationHeader(TokenResponse tokenResponse)
        {
            if (tokenResponse != null)
            {
                if (this.TokenItem != null)
                    this.TokenItem.Import(tokenResponse);
                else
                    this.TokenItem = TokenItem.Create(tokenResponse);
                this.CurrentAuthenticationHeader = new Sidvall.Net.AuthenticationHeader()
                {
                    Scheme = tokenResponse.TokenType,
                    Parameter = tokenResponse.AccessToken,
                };
            }
            else
            {
                this.TokenItem = new TokenItem();
                this.CurrentAuthenticationHeader = null;
            }
        }

        #endregion
        #region LoadAuthenticationHeaderAsync

        public async Task LoadAuthenticationHeaderAsync(TokenResponse tokenResponse, bool storeInFile)
        {
            await LoadAuthenticationHeaderAsync(tokenResponse, (bool?)storeInFile).ConfigureAwait(false);
        }
        protected virtual async Task LoadAuthenticationHeaderAsync(TokenResponse tokenResponse, bool? storeInFile)
        {
            LoadAuthenticationHeader(tokenResponse);
            if ((this.UseTokenFile) && (!string.IsNullOrWhiteSpace(this.TokenFilePath)) && (Sidvall.SystemContext.Current.UserStorageManager != null))
            {
                if (storeInFile == true)
                {
                    var content = Sidvall.Serialization.SerializerManager.Current.SerializeObject(this.TokenItem);
                    await Sidvall.SystemContext.Current.UserStorageManager.WriteAllTextAsync(this.TokenFilePath, content).ConfigureAwait(false);
                }
                else if (storeInFile == false)
                {
                    if (await Sidvall.SystemContext.Current.UserStorageManager.FileExistsAsync(this.TokenFilePath).ConfigureAwait(false))
                        await Sidvall.SystemContext.Current.UserStorageManager.DeleteFileAsync(this.TokenFilePath).ConfigureAwait(false);
                }
                else
                {
                    storeInFile = await Sidvall.SystemContext.Current.UserStorageManager.FileExistsAsync(this.TokenFilePath).ConfigureAwait(false);
                    if (storeInFile == true)
                    {
                        var content = Sidvall.Serialization.SerializerManager.Current.SerializeObject(this.TokenItem);
                        await Sidvall.SystemContext.Current.UserStorageManager.WriteAllTextAsync(this.TokenFilePath, content).ConfigureAwait(false);
                    }
                }
            }
            if (tokenResponse != null)
            {
                this.IsSaving = true;
                SecurityManager.OnAuthenticationHeaderChanged(tokenResponse);
                this.IsSaving = false;
            }
        }

        #endregion
        #region LoadTokenItem

        public void LoadTokenItem(string content)
        {
            var tokenItem = Sidvall.Serialization.SerializerManager.Current.TryDeserialize<TokenItem>(content, () =>
            {
                return null;
            });
            LoadTokenItem(tokenItem);
        }
        public void LoadTokenItem(TokenItem tokenItem)
        {
            if (tokenItem == null)
                tokenItem = new TokenItem();
            this.TokenItem = tokenItem;
        }

        #endregion
        #region LoadTokenItemAsync

        public virtual async Task LoadTokenItemAsync(bool refresh)
        {
            if (!this.TokenItemIsLoaded)
            {
                var content = await ReadTokenFileAsync().ConfigureAwait(false);
                LoadTokenItem(content);
                this.TokenItemIsLoaded = true;
            }
            else if (refresh)
            {
                var content = await ReadTokenFileAsync().ConfigureAwait(false);
                LoadTokenItem(content);
            }
            else if (this.TokenItem == null)
            {
                this.TokenItem = new TokenItem();
            }
        }

        #endregion
        #region NewAuthenticationHeaderFromRefreshTokenAsync

        public virtual async Task<bool> NewAuthenticationHeaderFromRefreshTokenAsync()
        {
            TokenResponse tokenResponse;

            if (this.TokenItem == null)
            {
                await LoadTokenItemAsync(false).ConfigureAwait(false);
            }
            try
            {
                tokenResponse = await Sidvall.Security.SecurityManager.GetTokenResponseFromRefreshTokenAsync(this.AuthorityTokenEndpoint, this.ClientId, this.ClientSecret,
                    this.TokenItem.RefreshToken, null, CancellationToken.None).ConfigureAwait(false);
            }
            catch
            {
                tokenResponse = null;
            }
            if (tokenResponse != null)
            {
                await LoadAuthenticationHeaderAsync(tokenResponse, null).ConfigureAwait(false);
                return true;
            }
            return false;
        }

        #endregion
        #region NewAuthenticationHeaderFromResourceOwnerPasswordAsync

        public virtual async Task<bool> NewAuthenticationHeaderFromResourceOwnerPasswordAsync(string userName, string password, string scope)
        {
            TokenResponse tokenResponse;

            try
            {
                tokenResponse = await Sidvall.Security.SecurityManager.GetTokenResponseFromResourceOwnerPasswordAsync(this.AuthorityTokenEndpoint, this.ClientId, this.ClientSecret,
                    userName, password, scope, null, CancellationToken.None).ConfigureAwait(false);
            }
            catch
            {
                tokenResponse = null;
            }
            if (tokenResponse != null)
            {
                await LoadAuthenticationHeaderAsync(tokenResponse, null).ConfigureAwait(false);
                return true;
            }
            return false;
        }

        #endregion
        #region ReadTokenFileAsync

        public virtual async Task<string> ReadTokenFileAsync()
        {
            if ((this.UseTokenFile) && (Sidvall.SystemContext.Current.UserStorageManager != null))
            {
                try
                {
                    if (await Sidvall.SystemContext.Current.UserStorageManager.FileExistsAsync(this.TokenFilePath).ConfigureAwait(false))
                        return await Sidvall.SystemContext.Current.UserStorageManager.ReadAllTextAsync(this.TokenFilePath).ConfigureAwait(false);
                }
                catch (System.Exception)
                {
                    return null;
                }
            }
            return null;
        }

        #endregion

        #region OnAuthenticationHeaderChanged

        protected virtual void OnAuthenticationHeaderChanged(object sender, TokenResponseEventArgs e)
        {
            if (this.IsSaving)
                return;
            LoadAuthenticationHeader(e?.TokenResponse);
        }

        #endregion

        #endregion
        #region Constructors

        public AuthenticationStrategy() : this(true)
        {
        }
        public AuthenticationStrategy(bool useTokenFile)
        {
            this.UseTokenFile = useTokenFile;
            SecurityManager.AuthenticationHeaderChanged += OnAuthenticationHeaderChanged;
        }

        #endregion
    }
}
